package com.codingman.data.mybatis.support;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.BeanUtils;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import com.codingman.core.utils.StringUtil;

public class TableInfo{
	private static final Map<Class<?>, TableInfo> TABLE_CACHE = new ConcurrentHashMap<>(128);

	/**
	 * Table name
	 */
	private String tableName;

	/**
	 * Primary key attribute name
	 */
	private String pkPropertyName;

	/**
	 * Column name corresponding to primary key
	 */
	private String pkColumnName;

	/**
	 * Map of attribute name and field name mapping relationship
	 */
	private List<Field> fields = new ArrayList<Field>();
	
	/**
	 * Map of attribute name and field name mapping relationship
	 */
	private Map<String, String> fieldColumnMap = new HashMap<String, String>();

	/**
	 * Field type
	 */
	private Map<String, Class<?>> fieldTypeMap = new HashMap<String, Class<?>>();

	private TableInfo(Class<?> clazz) {
		init(clazz);
	}

	public static TableInfo forClass(Class<?> entityClass) {
		TableInfo info = TABLE_CACHE.get(entityClass);
		if (info == null) {
			info = new TableInfo(entityClass);
			TABLE_CACHE.put(entityClass, info);
		}

		return info;
	}

	
	/**
	 * According to the annotation initialization table information,
	 *
	 * @ param clazz class of entity class
	 */
	private void init(Class<?> clazz) {
		this.tableName = this.doTableName(clazz);

		for (Field field : clazz.getDeclaredFields()) {

			// skip static fields
			// fields with @ transient annotation
			// simple type
			if (Modifier.isStatic(field.getModifiers()) || field.isAnnotationPresent(Transient.class)
					|| !BeanUtils.isSimpleValueType(field.getType())) {
				continue;
			}

			String fieldName = field.getName();
			String columnName = this.doColumnName(field);

			// Primary key information: for fields with @ ID annotation
			this.doPrimaryKey(field, columnName);
			
			// Put the column corresponding to the field into the map
			PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(clazz, fieldName);
			if (descriptor != null && descriptor.getReadMethod() != null && descriptor.getWriteMethod() != null) {
				this.fieldColumnMap.put(fieldName, columnName);
				this.fieldTypeMap.put(fieldName, field.getType());
				this.fields.add(field);
			}
		}
	}

	private void doPrimaryKey(Field field, String pkColumnName) {
		String fieldName = field.getName();
		
		if (field.isAnnotationPresent(Id.class) || (fieldName.equalsIgnoreCase("id") && pkPropertyName == null)) {
			this.pkPropertyName = fieldName;
			this.pkColumnName = pkColumnName;
		}
	}

	private String doTableName(Class<?> clazz) {
		return clazz.isAnnotationPresent(Table.class) ? clazz.getAnnotation(Table.class).value()
				: clazz.getSimpleName();
	}
	
	private String doColumnName(Field field) {
		return field.isAnnotationPresent(Column.class) ? field.getAnnotation(Column.class).value()
				: StringUtil.camelToSnake(field.getName());
	}

	public String getName() {
		return this.tableName;
	}

	public String getPKConlumn() {
		return this.pkColumnName;
	}
	
	public Collection<String> getColumns() {
		return this.fieldColumnMap.values();
	}
}
